namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Text;
  using System.Runtime;
  using System.Runtime.InteropServices;

  using PEA.DOS;

  /// <summary>
  /// NT Header
  /// </summary>
  public unsafe struct NTHeader
  {
    #region Statics
    public static NTHeader it()
    {
      return new NTHeader();
    }
    #endregion
    #region Consts
    public const UInt32     PEValue = 0x00004550;       //"PE\0\0"
    public const UInt32     DefaultDosBodySize = 0x320; //0x320+0x80 = 0x400 (512Bytes)
    #endregion
    #region Fields
    public DOSHeader        MZ;
    public UInt32           PE;
    public COFFHeader       CF;
    #endregion
    #region Internal Fields
    internal bool _IsPEFormat;
    internal DataDirectory[] directories;
    #endregion
    #region Properties
    public PEType PEType
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.PEType : PEType.Unknown;
      }
    }
    public OptionalHeader PEHeader
    {
      get
      {
        return this.CF.PEHeader;
      }
    }
    public SectionHeader[] Sections
    {
      get
      {
        return this.CF.Sections;
      }
      set
      {
        this.CF.Sections = value;
      }
    }
    public ImageDataDirectory this[UInt32 index]
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader[index] : ImageDataDirectory.it();
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader[index] = value;
        }
      }
    }
    public ImageDataDirectory this[DirectoryEntries entry]
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader[entry] : ImageDataDirectory.it();
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader[entry] = value;
        }
      }
    }
    public DataDirectory[] Directories
    {
      get
      {
        return this.directories;
      }
    }
    public bool IsPEFormat
    {
      get
      {
        return this._IsPEFormat;
      }
    }
    public bool IsPE32
    {
      get
      {
        return this.PEHeader is OptionalHeader32;
      }
    }
    public bool IsPE64
    {
      get
      {
        return this.PEHeader is OptionalHeader64;
      }
    }
    public bool HasPEHeader
    {
      get
      {
        return this.PEHeader != null;
      }
    }
    public bool HasSections
    {
      get
      {
        return this.Sections != null && this.Sections.Length > 0;
      }
    }
    public SubSystem SubSystemAttribute
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SubSystemAttribute : SubSystem.UNKNOWN;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SubSystemAttribute = value;
        }
      }
    }
    public DllAttributes DllAttributes
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.DllAttributes : DllAttributes.UNKNOWN;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.DllAttributes = value;
        }
      }
    }
    public UInt32 AddressOfEntryPoint
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.AddressOfEntryPoint : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.AddressOfEntryPoint = value;
        }
      }
    }
    public UInt32 BaseOfCode
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.BaseOfCode : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.BaseOfCode = value;
        }
      }
    }
    public UInt32 BaseOfData
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.BaseOfData : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.BaseOfData = value;
        }
      }
    }
    public UInt32 FileAlignment
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.FileAlignment : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.FileAlignment = value;
        }
      }
    }
    public UInt32 SectionAlignment
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SectionAlignment : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SectionAlignment = value;
        }
      }
    }
    public UInt64 ImageBase
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.ImageBase : (UInt64)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.ImageBase = value;
        }
      }
    }
    public UInt32 SizeOfCode
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfCode : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfCode = value;
        }
      }
    }
    public UInt32 SizeOfHeaders
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfHeaders : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfHeaders = value;
        }
      }
    }
    public UInt32 SizeOfImage
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfImage : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfImage = value;
        }
      }
    }
    public UInt32 SizeOfInitializedData
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfInitializedData : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfInitializedData = value;
        }
      }
    }
    public UInt32 SizeOfUninitializedData
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfUninitializedData : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfUninitializedData = value;
        }
      }
    }
    public UInt64 SizeOfHeapCommit
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfHeapCommit : (UInt64)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfHeapCommit = value;
        }
      }
    }
    public UInt64 SizeOfHeapReserve
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfHeapReserve : (UInt64)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfHeapReserve = value;
        }
      }
    }
    public UInt64 SizeOfStackCommit
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfStackCommit : (UInt64)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfStackCommit = value;
        }
      }
    }
    public UInt64 SizeOfStackReserve
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.SizeOfStackReserve : (UInt64)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.SizeOfStackReserve = value;
        }
      }
    }
    public UInt32 Win32VersionValue
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.Win32VersionValue : (UInt32)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.Win32VersionValue = value;
        }
      }
    }

    public UInt16 MajorOperatingSystemVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MajorOperatingSystemVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MajorOperatingSystemVersion = value;
        }
      }
    }
    public UInt16 MinorOperatingSystemVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MinorOperatingSystemVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MinorOperatingSystemVersion = value;
        }
      }
    }
    public UInt16 MajorImageVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MajorImageVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MajorImageVersion = value;
        }
      }
    }
    public UInt16 MinorImageVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MinorImageVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MinorImageVersion = value;
        }
      }
    }

    public UInt16 MajorSubsystemVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MajorSubsystemVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MajorSubsystemVersion = value;
        }
      }
    }
    public UInt16 MinorSubsystemVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MinorSubsystemVersion : (UInt16)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MinorSubsystemVersion = value;
        }
      }
    }

    public Byte MajorLinkerVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MajorLinkerVersion : (Byte)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MajorLinkerVersion = value;
        }
      }
    }
    public Byte MinorLinkerVersion
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.MinorLinkerVersion : (Byte)0;
      }
      set
      {
        if (this.HasPEHeader)
        {
          this.PEHeader.MinorLinkerVersion = value;
        }
      }
    }

    public UInt32 NumberOfDirectories
    {
      get
      {
        return this.HasPEHeader ? this.PEHeader.NumberOfDirectories : 0;
      }
    }

    #endregion
    #region Methods
    #region Read/Write
    public bool Read (BinaryReader reader)
    {
      bool valid = false;

      if (reader!=null)
      {
        if (valid = this.MZ.Read(reader))
        {
          UInt32 pe = Utils.Read32(reader);

          if (this._IsPEFormat = (valid = (pe == PEValue)))
          {
            this.PE = PEValue;

            if (valid = this.CF.Read(reader))
            {
              ////NOTICE: NOT ALL THE CLASSES SUPPORT PE64 YET
              //if (this.IsPE64)
              //{
              //  throw new NotImplementedException("PE64 is not fully supported!");
              //}

              this.ReadDirectories(reader, this.directories = new DataDirectory[this.NumberOfDirectories]);
            }
          }
        }
      }
      return valid;
    }
    public void Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        this.MZ.Write(writer);

        Utils.Write32(writer,this.PE = PEValue);

        this._IsPEFormat = true;
        
        this.CF.Write(writer);

        this.WriteDirectories(writer);
      }
    }
    public UInt32 GetFilePointer(UInt32 RVA)
    {
      UInt32 pointer = 0;
      if (this.HasSections)
      {
        for (int i = 0; i < this.Sections.Length; i++)
        {
          if (this.Sections[i].GetFilePointer(RVA,ref pointer))
          {
            return pointer;
          }
        }
      }
      return 0;
    }
    #endregion
    #region Define Headers
    public NTHeader Define(FileAttributes fileAttributes, UInt32 sectionCount)
    {
      return this.Define(PEType.PE32, fileAttributes, new SectionHeader[sectionCount]);
    }
    public NTHeader Define(FileAttributes fileAttributes, params SectionHeader[] sections)
    {
      return this.Define(PEType.PE32, fileAttributes, sections);
    }
    public NTHeader Define(PEType peType, FileAttributes fileAttributes, UInt32 sectionCount)
    {
      return this.Define(peType, fileAttributes, new SectionHeader[sectionCount]);
    }
    public NTHeader Define(PEType peType, FileAttributes fileAttributes, params SectionHeader[] sections)
    {
      OptionalHeader optionalHeader = null;

      this.MZ = DOSHeader.define(DefaultDosBodySize, Stubs.PE);

      if (peType == PEType.PE32)
      {
        optionalHeader = OptionalHeader32.it();
      }
      else if (peType == PEType.PE64)
      {
        optionalHeader = OptionalHeader64.it();
      }      
      if (optionalHeader != null)
      {
        this.PE = PEValue;
        this._IsPEFormat = true;
        this.CF = COFFHeader.define(optionalHeader, sections, fileAttributes);
      }
      return this;
    }
    #endregion
    #region ToString
    public override string ToString()
    {
      StringBuilder builder = new StringBuilder();

      builder.Append(this.MZ.ToString());

      if (this.IsPEFormat)
      {
        builder.Append(this.CF.ToString());
      }
      return builder.ToString();
    }
    #endregion
    #region Directories
    internal void ReadDirectories(BinaryReader reader,DataDirectory[] directories)
    {
      if (reader != null)
      {
        directories[(Int32)DirectoryEntries.Export]     = ExportDirectory.from(reader, this);
        directories[(Int32)DirectoryEntries.Import]     = ImportDirectory.from(reader, this);
        directories[(Int32)DirectoryEntries.Relocation] = RelocationDirectory.from(reader, this);
        directories[(Int32)DirectoryEntries.IAT]        = IATDirectory.from(reader, this);
      }
    }
    internal void WriteDirectories(BinaryWriter writer)
    {
      if (writer != null)
      {
      }
    }
    #endregion
    #endregion
  }
}